﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;

using RayDen.Library.Core;
using RayDen.Library.Core.Noise;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Data
{
    public partial class GenericExpressionEvaluator<TValue>
    {
        protected Expression ReadFunction(TextReader reader, Expression arrayParameter)
        {
            var commandName = reader.ReadWhile(c => c != '<').Trim('$', '<');
            //var commandArgs = reader.ReadWhile(c => c != '>').Trim('>').Split(',').Select(i => i.Trim(',', ' ')).ToArray();
            string[] commandArgs = new string[0];


            return CreateFunctionCall(commandName, commandArgs, reader, arrayParameter);
        }

        

        protected Expression CreateFunctionCall(string functionName, string[] argsVal, TextReader reader, Expression arrayParameter)
        {
            Expression[] arg = new[] { Expression.Empty() };
            Expression<Func<double, double, double, float>> noise3dExpression = (a, b, c) => NoiseProvider.Instance.Noise(new Vector((float)a, (float)b, (float)c));

            switch (functionName)
            {
                
                case "invert":
                    {
                        var functionCall = SingleParameterFunctionCall<float, RgbSpectrum>(reader, arrayParameter,
                            f => 1-f,
                            f => RgbSpectrum.UnitSpectrum() - f);
                        if (functionCall != null) return functionCall;
                    }
                    break;
                case "n":
                    {
                        var functionCall = SingleParameterFunctionCall<float, RgbSpectrum>(reader, arrayParameter,
                            f => NoiseProvider.Instance.Noise(new Vector(f)),
                            f => new RgbSpectrum(NoiseProvider.Instance.Noise(new Vector(f))));
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "ns":
                    var args = new List<Expression>();
                    while (args.Count < 3)
                    {
                        if (char.IsDigit((char)reader.Peek()))
                        {
                            args.Add(ReadOperand(reader));
                        }
                        else if (char.IsLetter((char)reader.Peek()))
                        {
                            args.Add(ReadParameter(reader, arrayParameter));
                        }
                        reader.Read();
                    }
                    arg = args.ToArray();
                    return Expression.Invoke(noise3dExpression, arg);
                case "circ":
                    {
                        var functionCall = DoubleParameterFunctionCall<double, RgbSpectrum>(reader, arrayParameter,
                                                                        (a, b) => NoiseProvider.SineWave((float)a + (float)b),
                                                                        (a, b) => NoiseProvider.SineWave((float)a) * b.Apply(NoiseProvider.SineWave),
                                                                        (a, b) => a.Apply(NoiseProvider.SineWave) + b.Apply(NoiseProvider.SineWave));
                        if (functionCall != null) return functionCall;
                    }
                    break;

                case "n3D":
                    {
                        var functionCall = SingleParameterFunctionCall<RgbSpectrum, RgbSpectrum>(reader, arrayParameter,
                            f => NoiseProvider.Instance.Noise3D(new Vector(f.c1)),
                            f => NoiseProvider.Instance.Noise3D(new Vector(f)));
                        if (functionCall != null) return functionCall;
                    }
                    break;
                case "t":
                    {
                        var functionCall = DoubleParameterFunctionCall<double, RgbSpectrum, double>(reader, arrayParameter,
                                                                        (a, b) => NoiseProvider.Instance.Turbulence(new Vector((float)a), (int)b),
                                                                        (a, b) => NoiseProvider.Instance.Turbulence(b, (int)a),
                                                                        (a, b) => NoiseProvider.Instance.Turbulence(b, (int)a.c1));
                        if (functionCall != null) return functionCall;
                    }
                    break;
                case "t3d":
                    {
                        var functionCall = DoubleParameterFunctionCall<double, RgbSpectrum, RgbSpectrum>(reader, arrayParameter,
                                                                        (a, b) => NoiseProvider.Instance.Turbulence3d(new Vector((float)a), (int)b),
                                                                        (a, b) => NoiseProvider.Instance.Turbulence3d(b, (int)a),
                                                                        (a, b) => NoiseProvider.Instance.Turbulence3d(b, (int)a.c1));
                        if (functionCall != null) return functionCall;
                    }
                    break;
                case "pow":
                    {
                        var functionCall = DoubleParameterFunctionCall<double, RgbSpectrum, double>(reader, arrayParameter,
                                                                        Math.Pow,
                                                                        (a, b) => Math.Pow(a, b.c1),
                                                                        (a, b) => Math.Pow(a.c1, b.c1));
                        if (functionCall != null) return functionCall;
                    }
                    break;
                case "saw":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, f => NoiseProvider.SineWave((float)f), f => f.Apply(NoiseProvider.SineWave));
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "sin":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, Math.Sin, f => new RgbSpectrum(MathLab.Sin(f.c1), MathLab.Sin(f.c2), MathLab.Sin(f.c3)));
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "cos":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, Math.Cos, f => new RgbSpectrum(MathLab.Cos(f.c1), MathLab.Cos(f.c2), MathLab.Cos(f.c3)));
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "exp":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, Math.Exp, RgbSpectrum.Exp);
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "sqrt":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, Math.Sqrt, RgbSpectrum.Sqrt);
                        if (functionCall != null) return functionCall;
                    }

                    break;
                case "spl":
                    {
                        var functionCall = SingleParameterFunctionCall(reader, arrayParameter, f => NoiseProvider.Spline((float)f), f => f.Apply(NoiseProvider.Spline));
                        if (functionCall != null) return functionCall;
                    }
                    break;
                //if (char.IsDigit((char)reader.Peek()))
                //{
                //    arg = new[] { ReadOperand(reader) };
                //    Expression<Func<double, double>> splExpression = f => (double)NoiseProvider.Spline((float)f);
                //    reader.Read();
                //    return Expression.Invoke(splExpression, arg);
                //}
                //else if (char.IsLetter((char)reader.Peek()))
                //{
                //    arg = new[] { ReadParameter(reader, arrayParameter) };
                //    Expression<Func<RgbSpectrum, double>> splExpression = f => (double)NoiseProvider.Spline(f.c1);
                //    reader.Read();
                //    return Expression.Invoke(splExpression, arg);
                //}
                //break;
            }
            throw new ArgumentException("Unknown function name " + functionName);
        }

        private Expression SingleParameterFunctionCall(TextReader reader, Expression arrayParameter, Func<double, double> fd, Func<RgbSpectrum, RgbSpectrum> fspd)
        {
            var arg_0 = ReadExpression(reader, arrayParameter);
            if (arg_0.Type == typeof(double))
            {
                Expression<Func<double, double>> expr = f => fd(f);
                return Expression.Invoke(expr, arg_0);
            }
            else if (arg_0.Type == typeof(RgbSpectrum))
            {
                Expression<Func<RgbSpectrum, RgbSpectrum>> expr =
                    f => fspd(f);
                return Expression.Invoke(expr, arg_0);
            }
            return null;
        }


        private Expression SingleParameterFunctionCall<TFirst, TSecond>(TextReader reader, Expression arrayParameter, Func<TFirst, TFirst> fd, Func<TSecond, TSecond> fspd)
        {
            var arg_0 = ReadExpression(reader, arrayParameter);
            if (arg_0.Type == typeof(TFirst))
            {
                Expression<Func<TFirst, TFirst>> expr = f => fd(f);
                return Expression.Invoke(expr, arg_0);
            }
            else if (arg_0.Type == typeof(TSecond))
            {
                Expression<Func<TSecond, TSecond>> expr =
                    f => fspd(f);
                return Expression.Invoke(expr, arg_0);
            }
            return null;
        }
        private Expression DoubleParameterFunctionCall<TFirst, TSecond>(TextReader reader,
            Expression arrayParameter,
            Func<TFirst, TFirst, TFirst> fd,
            Func<TFirst, TSecond, TSecond> fc,
            Func<TSecond, TSecond, TSecond> fspd)
        {
            var arg_0 = ReadExpression(reader, arrayParameter);
            reader.Read();
            var arg_1 = ReadExpression(reader, arrayParameter);
            reader.Read();

            if (arg_1.Type == arg_0.Type)
            {
                if (arg_0.Type == typeof(TFirst))
                {
                    Expression<Func<TFirst, TFirst, TFirst>> expr = (a, b) => fd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_1);
                }
                else if (arg_0.Type == typeof(TSecond))
                {
                    Expression<Func<TSecond, TSecond, TSecond>> expr = (a, b) => fspd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_1);
                }
            }
            else
            {
                Expression<Func<TFirst, TSecond, TSecond>> expr = (a, b) => fc(a, b);
                return Expression.Invoke(expr, arg_0.Type == typeof(TFirst) ? arg_0 : arg_1, arg_0.Type == typeof(TFirst) ? arg_1 : arg_0);
            }


            return null;
        }


        private Expression DoubleParameterFunctionCall<TFirst, TSecond, TResult>(TextReader reader,
           Expression arrayParameter,
           Func<TFirst, TFirst, TResult> fd,
           Func<TFirst, TSecond, TResult> fc,
           Func<TSecond, TSecond, TResult> fspd)
        {
            var arg_0 = ReadExpression(reader, arrayParameter);
            var arg_1 = ReadExpression(reader, arrayParameter);

            if (arg_1 == null)
            {
                if (arg_0.Type == typeof(TFirst))
                {
                    Expression<Func<TFirst, TFirst, TResult>> expr = (a, b) => fd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_0);
                }
                else if (arg_0.Type == typeof(TSecond))
                {
                    Expression<Func<TSecond, TSecond, TResult>> expr = (a, b) => fspd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_0);
                }
            }

            if (arg_1.Type == arg_0.Type)
            {
                if (arg_0.Type == typeof(TFirst))
                {
                    Expression<Func<TFirst, TFirst, TResult>> expr = (a, b) => fd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_1);
                }
                else if (arg_0.Type == typeof(TSecond))
                {
                    Expression<Func<TSecond, TSecond, TResult>> expr = (a, b) => fspd(a, b);
                    return Expression.Invoke(expr, arg_0, arg_1);
                }
            }
            else
            {
                Expression<Func<TFirst, TSecond, TResult>> expr = (a, b) => fc(a, b);
                return Expression.Invoke(expr, arg_0.Type == typeof(TFirst) ? arg_0 : arg_1, arg_0.Type == typeof(TFirst) ? arg_1 : arg_0);
            }


            return null;
        }
    }
}